//
// Created by admin on 2024/8/1.
//
#include "hal-i2c.h"

#include <sys/stat.h>

#include "port_stm32.h"

#define I2C_TIMEOUT_TICK	200

__attribute__((weak)) void i2cEventCallback(void *i2c, int event, uint32_t size) {
}

i2c_status_t i2c_is_ready(void *i2c, uint16_t devAddr) {
	i2c_status_t ret = I2C_OK;

	HAL_StatusTypeDef status = HAL_I2C_IsDeviceReady(i2c, devAddr, 1, I2C_TIMEOUT_TICK);
	switch (status) {
	case HAL_OK:
		ret = I2C_OK;
		break;
	case HAL_BUSY:
		ret = I2C_BUSY;
		break;
	case HAL_TIMEOUT:
		ret = I2C_TIMEOUT;
		break;
	default:
		ret = I2C_ERROR;
		break;
	}
	return ret;
}

int i2cWrite(void *i2c, uint16_t devAddr, uint8_t *buff, uint32_t size, uint8_t stop) {
	int ret;

	if (size == 0) {
		return i2c_is_ready(i2c, devAddr);
	}

	HAL_StatusTypeDef status = HAL_OK;
	uint32_t start_tick      = millis();
	uint32_t err             = 0;

	// check transfer options and store it in the I2C handle
#if defined(I2C_OTHER_FRAME)
	uint32_t XferOptions;
	if (stop == 0) {
		XferOptions = I2C_OTHER_FRAME;
	} else {
		XferOptions = I2C_OTHER_AND_LAST_FRAME;
	}
	((I2C_HandleTypeDef *) i2c)->XferOptions = XferOptions;
#endif

	do {
#if defined(I2C_OTHER_FRAME)
		status = HAL_I2C_Master_Seq_Transmit_IT(i2c, devAddr, buff, size, XferOptions);
#else
		status = HAL_I2C_Master_Transmit_IT(i2c, devAddr, buff, size);
#endif
	} while (status == HAL_BUSY && millis() - start_tick < I2C_TIMEOUT_TICK);

	ret = (status == HAL_OK ? I2C_OK : I2C_BUSY);

	// wait for transfer completion
	if (ret == I2C_OK) {
		start_tick = millis();
		while (HAL_I2C_GetState(i2c) != HAL_I2C_STATE_READY &&
			   millis() - start_tick < I2C_TIMEOUT_TICK) {
			if (HAL_I2C_GetError(i2c) != HAL_I2C_ERROR_NONE) {
				break;
			}
		}

		err = HAL_I2C_GetError(i2c);
		if ((millis() - start_tick > I2C_TIMEOUT_TICK) ||
			((err & HAL_I2C_ERROR_TIMEOUT) == HAL_I2C_ERROR_TIMEOUT)) {
			ret = I2C_TIMEOUT;
		} else {
			if ((err & HAL_I2C_ERROR_AF) == HAL_I2C_ERROR_AF) {
				ret = I2C_NACK_DATA;
			} else if (err != HAL_I2C_ERROR_NONE) {
				ret = I2C_ERROR;
			}
		}
	}

	return ret;
}

int i2cRead(void *i2c, uint16_t devAddr, uint8_t *buff, uint32_t size, uint8_t stop) {
	int ret = I2C_OK;

	HAL_StatusTypeDef status = HAL_OK;
	uint32_t start_tick      = millis();
	uint32_t err             = 0;

	// perform blocking read into buffer
#if defined(I2C_OTHER_FRAME)
	uint32_t XferOptions;
	if (stop == 0) {
		XferOptions = I2C_OTHER_FRAME;
	} else {
		XferOptions = I2C_OTHER_AND_LAST_FRAME;
	}
	((I2C_HandleTypeDef *) i2c)->XferOptions = XferOptions;
#endif

	do {
#if defined(I2C_OTHER_FRAME)
		status = HAL_I2C_Master_Seq_Receive_IT(i2c, devAddr, buff, size, XferOptions);
#else
		status = HAL_I2C_Master_Receive_IT(i2c, devAddr, buff, size);
#endif
	} while (status == HAL_BUSY && millis() - start_tick < I2C_TIMEOUT_TICK);

	ret = (status == HAL_OK ? I2C_OK : I2C_BUSY);

	// wait for transfer completion
	if (ret == I2C_OK) {
		start_tick = millis();
		while ((HAL_I2C_GetState(i2c) != HAL_I2C_STATE_READY) &&
			   (millis() - start_tick < I2C_TIMEOUT_TICK)) {
			if (HAL_I2C_GetError(i2c) != HAL_I2C_ERROR_NONE) {
				break;
			}
		}

		err = HAL_I2C_GetError(i2c);
		if ((millis() - start_tick > I2C_TIMEOUT_TICK) ||
			((err & HAL_I2C_ERROR_TIMEOUT) == HAL_I2C_ERROR_TIMEOUT)) {
			ret = I2C_TIMEOUT;
		} else {
			if ((err & HAL_I2C_ERROR_AF) == HAL_I2C_ERROR_AF) {
				ret = I2C_NACK_DATA;
			} else if (err != HAL_I2C_ERROR_NONE) {
				ret = I2C_ERROR;
			}
		}
	}

	return ret;
}

int i2cRegRead(void *i2c, uint16_t devAddr, uint16_t reg, uint8_t addrSize, uint8_t *buff, uint32_t size) {
	int ret = I2C_OK;

	HAL_StatusTypeDef status = HAL_OK;
	uint32_t start_tick      = millis();
	uint32_t err             = 0;

	do {
		status = HAL_I2C_Mem_Read(i2c, (devAddr << 1) | 0x01, reg,
								  addrSize == I2C_8BIT_ADDR ? I2C_MEMADD_SIZE_8BIT : I2C_MEMADD_SIZE_16BIT,
								  buff, size, I2C_TIMEOUT_TICK);
	} while (status == HAL_BUSY && millis() - start_tick < I2C_TIMEOUT_TICK);

	ret = (status == HAL_OK ? I2C_OK : I2C_BUSY);

	return ret;
}

int i2cRegWrite(void *i2c, uint16_t devAddr, uint16_t reg, uint8_t addrSize, uint8_t *buff, uint32_t size) {
	int ret = I2C_OK;

	HAL_StatusTypeDef status = HAL_OK;
	uint32_t start_tick      = millis();
	uint32_t err             = 0;

	do {
		status = HAL_I2C_Mem_Write(i2c, (devAddr << 1) | 0x00, reg,
								  addrSize == I2C_8BIT_ADDR ? I2C_MEMADD_SIZE_8BIT : I2C_MEMADD_SIZE_16BIT,
								  buff, size, I2C_TIMEOUT_TICK);
	} while (status == HAL_BUSY && millis() - start_tick < I2C_TIMEOUT_TICK);

	ret = (status == HAL_OK ? I2C_OK : I2C_BUSY);

	return ret;
}
